///<reference path="../typings/tsd.d.ts" />

interface AutoTableTHOptions {
    /** 格式是：标题|id   id可空*/
    title: string,
    /** 格式是： [] 表示 1，1  ，先横后竖 */
    span: (number | string)[],
    /**缓存th，避免重复生成。 用户不要使用它 */
    _cache?: string
}
//表头中的列信息
interface AutoTableFieldInfo {
    /** 当前列的序号*/
    index: number,
    /** 当前列所对应的th */
    ths: HTMLTableHeaderCellElement[],
    /**缓存当前列的宽度 */
    width?: number;
}
interface AutoTableColumns {
    formator?: (value: any, row: any) => string;
    tooltip?: (value: any, row: any) => string;
}
interface AutoTableOptions {
    /**初始化类名 */
    initClass?: string
    data?: any[],
    /**上表头， 格式是：
     * [
     *  [{title:'姓名|name',span:[2,1,,'style="width:50px"']}]
     * ] */
    header?: AutoTableTHOptions[][],
    header2?: AutoTableTHOptions[][],
    /**字段序列，若不给，则自动根据数据的默认顺序输出 */
    headerFields?: string[],
    /**下表头的字段顺序。必须给出 */
    header2Fields?: string[],

    isFooter?: boolean,
    /**是否分页 */
    isPageable?: boolean,
    /**页码 */
    pageNumber?: number,
    /**单页行数 */
    pageSize?: number,
    pageSizeList?: number[],

    sortFields?: string[],
    /**纵向合并的字段 */
    mergeFields?: string[],
    /**每一列的宽度 */
    columnsWidths?: number[],

    zeroShowAs?: string,
    nullShowAs?: string,
    prefix?: string,
    /**是否显示序号列，如果为true,给每一行数据自动加入 _rownum 的属性.表头要预留位置。freezeFields需要指明 _rownum ，才能锁定该列 */
    isRownumber?: boolean,
    isScrollable?: boolean,
    freezeFields?: string[],
    /**滚动时相对的容器 */
    freezeContainer?: string | Window,
    /**选择模式：不选，单选或多选*/
    selectMode?: AUTO.SelectMode,
    /**用于指定列的格式化，提示等 */
    columns?: any,
    /**显示在行上的提示 */
    tooltipOnRow?: (any) => string,
    /**将关键的字段绑定到行上 比如：["ID"]  data-id='1' ,绑定后属性会变小写  */
    keydataOnRow?: string[],
    editUrl?: string,
    editTable?: string,
    editKeyFields?: string[],
    /**可编辑列，不要合并以及不要columns.formator */
    editFields?: string[],
    /**过滤可编辑行，只有返回true,行才可编辑。 默认 null，表示所有行可编辑*/
    editOn?: (field: any, row: any[]) => boolean,
    editBeforeUpload?: (upKv: any, field: string) => boolean,
    /**是否显示编辑提示框*/
    editTopHover?: boolean;
    /**过滤函数，只显示返回true的数据行 */
    filter?: (row: any) => boolean,
    isSaveable?: boolean,
    saveURL?: string,
    /**折叠功能*/
    /**折叠显示所在的列*/
    treeField?: string,
    /**first,second分别是要比较的两行。
    返回true,表示第2行是第1行的子节点，要下沉一格.
    返回false,表示要向上提一级*/
    treeIndentOn?: (first, second) => boolean,
    /**缩进时，默认要显示的级数。从0开始*/
    treeIndentShowLevel?: number,
    /**用于判断开始行的函数，就是确定indent=0的那些行。
        如果不提供本函数，表示数据已经是按父子的关系，排好了序。
        如果提供，那么要对显示的数据进行一次重排序，以便让它们能按父子的关系进行排序。 */
    treeStartOn?: (row) => boolean
}


module AUTO {
    export enum SelectMode { NONE, SINGLE, MULTI }
    export class AutoTable {
        private static DefaultClassName = "autotable";
        private static DefaultEditClassName = "autoEdit";
        private static DefaultEditInputClassName = "autoEditinput";
        private static DefaultFreezeClassName = "frzTD";
        private static DefaultEventNamespace = ".autoTableEvent";
        private static DefaultSelectedClassName = "autoselected";
        private static DefaultSaveButtonClassName = "autoSave";
        private static DefaultCellEdited = 'cellEdited';
        private static DefaultRownumID = "_rownum";
        private _opt: AutoTableOptions = {
            data: [],
            prefix: '',
            isRownumber: false,
            header: [],
            header2: [],
            headerFields: [],
            header2Fields: [],
            isFooter: true,
            zeroShowAs: '0',
            nullShowAs: '',
            isPageable: false,
            pageNumber: 1,
            pageSize: 20,
            pageSizeList: [10, 20, 50, 100],
            sortFields: [],
            mergeFields: [],
            columnsWidths: [],
            isScrollable: false,
            freezeFields: [],
            freezeContainer: window,
            selectMode: SelectMode.MULTI,
            columns: {},
            tooltipOnRow: null,
            keydataOnRow: [],
            editUrl: '',
            editTable: '',
            editKeyFields: [],
            editFields: [],
            editOn: null,
            editBeforeUpload: null,
            editTopHover: false,
            filter: null,
            isSaveable: false,
            saveURL: "",
            treeIndentOn: null,
            treeField: "",
            treeIndentShowLevel: 1,
            treeStartOn: null,
            initClass: "table table-striped table-bordered table-condensed table-hover"
        };
        public me: JQuery;
        private prefix: string = "";
        private thead: string = "";
        private tbody: string = "";
        private tfoot: string = "";
        private thead2: string = "";
        private tbody2: string = "";
        private tcolgroup: string = ""; //控制列宽
        private tContainer: JQuery; //控制滚动
        private tEditTopHover: JQuery;//编辑框上的提示
        /** 需要显示的数据行 */
        private showData: any[][];
        /**分页时，显示的起始行号*/
        private start: number;
        /**分页时，显示的结束行号*/
        private end: number;
        /** 需要显示总行数 */
        private rowCount: number;
        /** 总列数  */
        private columnsCount: number;
        /**是否可编辑 */
        private isEditable: boolean;
        /**上表身缩进的计数*/
        private nIndent: number;
        /**缩进时，所经历所父行的存储记录*/
        private arrIndented: any[];
        /**字段字典         */
        private fieldsDict: any = {};
        constructor(element: Element, options: AutoTableOptions) {
            this.me = $(element);
            this._opt = $.extend(this._opt, options);
            this.prefix = (this._opt.prefix || this.me.attr("id") || ("auto")) + "_";
            this.init();
            this.renderColgroup();//只生成一次
            this.renderThead();   //只生成一次
            this.renderTfoot();
            this.renderTbody();
            this.me.html(`${this.tcolgroup}<tbody>${(this.tbody + this.thead2 + this.tbody2)}</tbody>${this.thead}<tfoot>${this.tfoot}</tfoot>`);
            this.findDoms();
            this.readHeadIndex();
            //这些方法只绑定一次即可
            this.bindEvents();
            this.me.data('OriginData', this).trigger('loaded');
        }
        public reloadTbodyTfoot() {
            this.renderTfoot();
            this.renderTbody();
            this.me.find("tbody").html(this.tbody + this.thead2 + this.tbody2);
            this._opt.isFooter && this.me.find("tfoot").html(this.tfoot);
            this.findDoms();
            this.me.trigger("reloaded");
        }

        //递归排序所有的行。
        private rec_Treerows(parentRow: any, remainedRow: any[], sortedRow: any[]) {
            let scope = this;
            _.each(remainedRow, function (r, idx) {
                //如果已经标记过，则跳过它。
                if (r["_isMarked"]) return true;
                //如果该行是子级行，则压入，继续递归找该行的子组
                if (scope._opt.treeIndentOn(parentRow, r)) {
                    r["_isMarked"] = true;
                    sortedRow.push(r);
                    scope.rec_Treerows(r, remainedRow, sortedRow);
                }
            });
        }
        public init(): void {
            let scope = this;
            //1、是不是第一次调此函数。不是则添加样式，是则移除原事件.
            if (this.me.hasClass(AutoTable.DefaultClassName)) {
                this.me.off(AutoTable.DefaultEventNamespace);
                this.me.data("OriginData").tContainer.off(AutoTable.DefaultEventNamespace);
            }
            else {
                this.me.addClass(AutoTable.DefaultClassName + " " + this._opt.initClass);
            }
            //2、首先确定表各部分及外部container
            this.tContainer = $(this._opt.freezeContainer);


            //3、判断data是否是数组,并准备数据
            if (!_.isArray(this._opt.data)) {
                $.auto.alert("错误", "传入的数据不是数组，请检查！")
                return;
            }
            this.showData = _.isFunction(this._opt.filter) ?
                _.filter(this._opt.data, this._opt.filter) :
                this._opt.data;
            this.rowCount = this.showData.length;
            //3.5 如果是树数据，且提供了treeStartOn，那么要对数据进行排序
            if (_.isFunction(this._opt.treeStartOn)) {
                let sortedData = [];
                let startData = []
                let remainedData = [];
                _.each(this.showData, function (row) {
                    row["_isMarked"] = false;
                    scope._opt.treeStartOn(row) ? startData.push(row) : remainedData.push(row);
                });
                _.each(startData, function (row) {
                    sortedData.push(row);
                    scope.rec_Treerows(row, remainedData, sortedData);
                });
                this.showData = sortedData;
            }
            //4、如果没有给定headerFields,以及header,则要根据第一行数据生成。
            if (this._opt.data.length) {
                if (this._opt.headerFields.length == 0) {
                    for (let key in this._opt.data[0])
                        this._opt.headerFields.push(key);
                }
                if (this._opt.header.length == 0) {
                    let arrHead: AutoTableTHOptions[] = [];
                    for (let key in this._opt.data[0])
                        arrHead.push({ title: key, span: [] });
                    this._opt.header.push(arrHead);
                }
            }
            //5、插入序号列
            if (this._opt.isRownumber) {
                if (this._opt.headerFields.indexOf(AutoTable.DefaultRownumID) == -1) {
                    this._opt.headerFields.unshift(AutoTable.DefaultRownumID);
                    this._opt.header2Fields.length && this._opt.header2Fields.unshift(AutoTable.DefaultRownumID);
                }
            }
            scope.columnsCount = scope._opt.headerFields.length;
            //如果没有给出列宽度或给的不够。都要补齐 -1
            if (scope._opt.columnsWidths.length < scope.columnsCount) {
                _.times(scope.columnsCount - scope._opt.columnsWidths.length,
                    function () { scope._opt.columnsWidths.push(-1) });
            }
            //6、获取字段索引信息
            _.each(this._opt.headerFields, function (f, i) { scope.fieldsDict[f] = i });
            this._opt.header2Fields.length && _.each(this._opt.header2Fields, function (f, i) { scope.fieldsDict[f] = i });
            //7、判断编辑功能
            if (this._opt.editFields.length > 0 &&
                this._opt.editKeyFields.length > 0 &&
                this._opt.editUrl != '' &&
                this._opt.editTable != '') {
                this.isEditable = true;
                this.me.css('table-layout', 'fixed');

                //编辑提示框
                if (this._opt.editTopHover) {
                    this.tEditTopHover = $("autoEditTopHover");
                    if (!this.tEditTopHover.length) {
                        this.tEditTopHover = $("<div class='autoEditTopHover'>");
                        $('body').append(this.tEditTopHover);
                    }
                }
            }
        }
        private renderColgroup() {
            let cols = _.reduce(this._opt.columnsWidths, function (sum, w) {
                return sum + (w > 0 ? `<col style='width:${w}px;'/>` : `<col />`);
            }, "");
            this.tcolgroup = `<colgroup>${cols}</colgroup>`;
        }
        //渲染表头。表头以th为基本单元缓存
        private createTh(thOpt: AutoTableTHOptions): string {
            if (thOpt._cache) return thOpt._cache;
            let th = '', th_attr = '', th_text = '';
            let arr = thOpt.title.split("|");
            //如果有ID
            if (arr[1]) {
                //如果是排序，则标题显示为<a class="autoSort" >title</a> 否则 直接显示
                if (_.includes(this._opt.sortFields, arr[1])) {
                    th_text += `<a class='autoSort' data-field=${arr[1]}>${arr[0]}</a> `;
                }
                else {
                    th_text += arr[0];
                }
                th_attr += `id="${this.prefix + arr[1]}" data-field="${arr[1]}" `;
            }
            //没有ID
            else {
                th_text += arr[0];
            }
            //设置rowspan colspan
            (thOpt.span[0] && thOpt.span[0] != 1) && (th_attr += `colspan="${thOpt.span[0]}" `);
            (thOpt.span[1] && thOpt.span[1] != 1) && (th_attr += `rowspan="${thOpt.span[1]}" `);
            (thOpt.span[2]) && (th_attr += thOpt.span[2]);

            th = `<th ${th_attr}>${th_text}</th>`;
            thOpt._cache = th;
            return th;
        }
        /**flag=1 上表头   2：下表头 */
        public renderThead(flag = 1) {
            let scope = this;
            //当前表头参数
            let headeropt = flag == 1 ? this._opt.header : this._opt.header2;

            //新版本中，每一行是一个数组。但老版本是{}.老版本转换一下
            if (!_.isArray(headeropt[0])) {
                let header: AutoTableTHOptions[][] = [];
                _.each(headeropt, function (old_row) {
                    let row = [];
                    _.map(old_row, function (v, k) {
                        row.push({ title: k, span: v });
                    })
                    header.push(row);
                });
                if (flag == 1) {
                    this._opt.header = header;
                }
                else {
                    this._opt.header2 = header;
                }
                headeropt = header;
            }
            //生成表头内容
            let trs = _.reduce(headeropt, function (trs, row) {
                let ths = _.reduce(row, function (result, thOpt) {
                    return result += scope.createTh(thOpt);
                }, '');
                return trs += (flag == 1 ? `<tr>${ths}</tr>` : `<tr class="autoheader2">${ths}</tr>`);
            }, '');
            if (flag == 1)
                scope.thead = `<thead>${trs}</thead>`;
            else
                scope.thead2 = trs;

            if (flag == 1 && this._opt.header2Fields.length > 0) {
                scope.renderThead(2);
            }
        }
        //渲染表身，不再缓存行
        private createTR(row: any, rowidx: number, vMerge: any, flag = 1): string {
            let scope = this;
            let tr = '', tr_attr = '', tds = '';
            this._opt.tooltipOnRow && (tr_attr += `title="${this._opt.tooltipOnRow(row)}" `);

            if (this._opt.keydataOnRow) {
                _.each(this._opt.keydataOnRow, function (key) {
                    tr_attr += `data-${key}="${row[key]}" `;
                });
            }
            //循环生成td
            let headerfield = flag == 1 ? this._opt.headerFields : this._opt.header2Fields;
            _.each(headerfield, function (field, idx) {
                let td_attr = '', td_text = '', td_class = '';
                td_class += (scope.prefix + field);
                //添加列标识
                td_attr += `data-field=${field} `;
                //如果该列需要纵向合并，且该列值不同以上面一个值，则其是一个新单元格.向下搜索长度合并。
                //如果是null，刚不进行合并
                if (_.includes(scope._opt.mergeFields, field)) {
                    if (row[field] != null) {
                        //不等表示是新值，插入td. 否则表示与上一行相同，忽略生成td。
                        if (row[field] == vMerge[field]) {
                            return;//忽略生成td
                        }
                        else {
                            vMerge[field] = row[field];
                            let rowspan = 1;
                            while ((rowidx + rowspan) <= scope.end) {
                                if (scope.showData[rowidx + rowspan][field] == vMerge[field]) rowspan++;
                                else break;
                            }
                            rowspan > 1 && (td_attr += `rowspan="${rowspan}" `);
                        }
                    }
                    else {
                        vMerge[field] = row[field];
                    }
                }
                //求值
                let curColumn = <AutoTableColumns>scope._opt.columns[field];
                let value = row[field];
                if (curColumn && _.isFunction(curColumn.formator)) {
                    value = curColumn.formator(value, row);
                } else {
                    value = (value == 0 ? scope._opt.zeroShowAs : (value || scope._opt.nullShowAs));
                }
                //冻结
                scope._opt.freezeFields.indexOf(field) >= 0 && (td_class += (" " + AutoTable.DefaultFreezeClassName));
                //列序号 索引。
                td_attr += `data-index="${idx}" `;
                //td上的提示
                if (curColumn && _.isFunction(curColumn.tooltip)) {
                    td_attr += `title="${curColumn.tooltip(row[field], row)}" `;
                }
                //field是否缩进
                if (scope._opt.treeField == field && _.isFunction(scope._opt.treeIndentOn)) {
                    //此处循环比较隐晦。目的是当上表头时，进行缩进判断。  下表头时，直接用上表头的Indent进行绘制就好了。
                    //原本结构应写为：  if(flag==1)  while(true){ ....}    else { do nothing .}
                    //                  td_text+= .........
                    while (flag == 1) {
                        if (scope.arrIndented.length == 0) {
                            scope.arrIndented.push(rowidx);
                            scope.nIndent = 0;
                            break;
                        }
                        else {
                            let lastIndent = scope.arrIndented.slice(-1)[0];
                            //是子级
                            if (scope._opt.treeIndentOn(scope.showData[lastIndent], scope.showData[rowidx])) {
                                //缩进并保存该行
                                scope.nIndent++;
                                scope.arrIndented.push(rowidx);
                                break; //跳出来
                            } else { //不是子级
                                scope.arrIndented.pop();
                                scope.nIndent--;  //此处不跳，继续向上找上一级
                            }
                        }
                    }
                    scope.nIndent && (td_text += _.repeat('<div class="auto-indent"></div>', scope.nIndent));
                    //默认显示到1级。 但第1级的行肯定要为显示折叠，而0级是打开
                    //如果没有子级，则必须不显示该三角。但没有好办法判断，所以主动访问一下下一行来判断。
                    if (rowidx < scope.end && scope._opt.treeIndentOn(scope.showData[rowidx], scope.showData[rowidx + 1])) {
                        td_text += `<div class="auto-indent ${scope.nIndent <= scope._opt.treeIndentShowLevel - 1 ? "auto-expanded" : "auto-collapsed"}"></div>`;
                    } else {
                        td_text += '<div class="auto-indent"></div>';  //否则只加一个占位符就好了。
                    }
                    tr_attr += `data-indent="${scope.nIndent}" `; //注意，此处是向外层循环中，写入值 。写入到tr的属性上。
                }
                //可编辑
                if (scope.isEditable &&
                    (scope._opt.editOn == null || ((_.isFunction(scope._opt.editOn)) && scope._opt.editOn(field, row))) &&
                    _.includes(scope._opt.editFields, field)
                ) {
                    let kv = {};
                    _.each(scope._opt.editKeyFields, function (f) {
                        kv[f] = row[f];
                    });
                    //只绑定值，当前行的主键值。 url，table是全局使用，不用绑定。
                    td_class += (" " + AutoTable.DefaultEditClassName);
                    td_text += (`<input class="${AutoTable.DefaultEditInputClassName}" value="${value}" data-field="${field}" data-kv='${JSON.stringify(kv)}' data-old="${value}" />`);
                } else {
                    td_text += value;
                }
                tds += `<td class="${td_class}"  ${td_attr}>${td_text}</td>`;
            })
            tr = `<tr ${tr_attr}>${tds}</tr>`;
            return tr;
        }
        public renderTbody() {
            let scope = this;
            let trs = '';
            let trs2 = '';//保存下表身的数据行
            scope.start = 0, scope.end = this.showData.length - 1;
            if (scope.end < 0) return;
            //分页时，设置起始行
            if (this._opt.isPageable) {
                scope.start = (this._opt.pageNumber - 1) * this._opt.pageSize;
                scope.end = Math.min(this._opt.pageNumber * this._opt.pageSize - 1, scope.end);
            }
            //在生成body之前，设置一下缩进值
            scope.arrIndented = [];
            /** 用于纵向合并判定,第一次访问undefined*/
            let vMerge1 = {}, vMerge2 = {};
            _.each(this.showData, function (row, ridx) {
                row[AutoTable.DefaultRownumID] = ridx + 1;
                if (ridx >= scope.start && ridx <= scope.end) {
                    trs += scope.createTR(row, ridx, vMerge1, 1);
                    scope._opt.header2Fields.length && (trs2 += scope.createTR(row, ridx, vMerge2, 2));
                }
            });
            scope.tbody = trs;
            scope.tbody2 = trs2;
        }

        private pageCount: number;
        public renderTfoot() {
            if (!this._opt.isFooter) return;
            let scope = this;
            let td_attr = "", td_text = "";
            //临时保存下拉里的option,上页，下页
            let options = '', btn1 = '', btn2 = '';
            td_attr += (`colspan="${this.columnsCount}" `);
            if (this._opt.isPageable) {
                //页大小的列表
                this.pageCount = Math.floor((this.rowCount + this._opt.pageSize - 1) / this._opt.pageSize);
                _.each(this._opt.pageSizeList, function (psl) {
                    options += `<option  ${psl == scope._opt.pageSize ? 'selected="true"' : ''}>${psl}</option>`;
                });
                options += '<option value="all">全部</option>';
                td_text += (`共${this.rowCount}条&nbsp;<span>每页<select class="psSelect">${options}</select>条</span>`);
                //当前页码跳转
                options = '';
                for (let i = 1; i <= this.pageCount; i++) {
                    options += (i == scope._opt.pageNumber) ? `<option selected="true">${i}</option>` : `<option>${i}</option>`;
                }
                td_text += (`<span>跳转<select class="pnSelect">${options}</select>页</span>`);
                //上页，下页
                btn1 = `<button type="button" class="btn btn-default btn-xs" data-flag="prepage" ${this._opt.pageNumber == 1 ? "disabled" : ""}>上一页</button>`;
                btn2 = `<button type="button" class="btn btn-default btn-xs" data-flag="nextpage" ${this._opt.pageNumber >= this.pageCount ? "disabled" : ""}>下一页</button>`;
                td_text += (`<div class="btn-group autoPager">${btn1}${btn2}</div>`);

            } else {
                td_text += '<button type="button" class="btn btn-default btn-xs" data-flag="paging">启用分页</button>';
            }
            if (this._opt.isSaveable) {
                td_text += `<button type="button"  class="btn btn-default btn-xs ${AutoTable.DefaultSaveButtonClassName}" >导出</button>`;
            }
            this.tfoot = `<td ${td_attr}>${td_text}</td>`;
        }

        /** 绑定所有的事件 */
        public frzHead: JQuery;
        public frzTd: JQuery;
        private allEditInput: JQuery;
        private findDoms() {
            //这里要重新绑定
            if (this.isEditable) {
                //仅用于键盘跳转时，能更快速。要是跳转不用，可以去掉
                this.allEditInput = this.me.find("." + AutoTable.DefaultEditInputClassName);
            }
            if (this._opt.isScrollable) {
                this.frzHead = this.me.find("thead th");
                this.frzTd = this.me.find("." + AutoTable.DefaultFreezeClassName);
            }
            //每次重新生成表身时，都要按照默认的显示缩进的级别进行显示。
            this._opt.treeField != "" && this.hideTreeCollapse();
        }
        private readHeadIndex() {
            //第一次加载表头后，更新一下每个th所属的index
            let scope = this;
            //计算每一列对应哪些表头中的th
            let pos = [[], [], [], [], [], [], [], [], [], []];   //默认表头最大为10行。
            let x = 0, y = 0; //pos是二维数组，表示表头的分布着哪些th
            let thInCol = []; //每一列所包含的th
            let tmp1, tmp2;
            var allTheadrows = this.me.find("thead>tr");
            if (this._opt.header2Fields.length > 0) {
                allTheadrows = allTheadrows.add(this.me.find(".autoheader2"));
            }
            allTheadrows.each(function (iTR, tr) {
                $(tr).find('th').each(function (iTH, th: HTMLTableHeaderCellElement) {
                    let c = th.colSpan, r = th.rowSpan;
                    for (tmp1 = 0; tmp1 < c; tmp1++) { //先处理一列，让这一列增加该td
                        for (tmp2 = 0; tmp2 < r; tmp2++) {
                            //如果当前pos已有值，则向后移一格
                            while (typeof pos[y + tmp2][x + tmp1] !== 'undefined') {
                                x++;
                            }
                            pos[y + tmp2][x + tmp1] = th;
                        }
                        thInCol[x + tmp1] = thInCol[x + tmp1] || [];
                        thInCol[x + tmp1].push(th);
                    }
                    //处理下一个td之前，先修改x的值
                    x = x + th.colSpan;
                });
                ////处理下一个tr之前，先修改y的值
                x = 0;
                y++;
            });
            scope.fieldsDict = {}; //每个字段对应的列号,及表头列。  
            //上表头中出现的列了。
            $.each(this._opt.headerFields, function (i, field) {
                scope.fieldsDict[field] = {
                    index: i,
                    ths: thInCol[i]
                };
            });
            //下表头中出现的列了。下表列与上表列相重的部分，务必在同一列上。比如ny,dwmc这些，上下表头要一致！
            $.each(this._opt.header2Fields, function (i, field) {
                scope.fieldsDict[field] = {
                    index: i,
                    ths: thInCol[i]
                };
            });
        }

        /**保存目前的排序信息 */
        private sortInfo: any = {};
        private hidedFields: string[] = [];
        /**
         * 绑定点击变色，页面大小，页数变化，排序，锁定表头及列
         */
        public bindEvents() {
            let scope = this;
            //单击变色,单击输入框则不变色
            if (this._opt.selectMode != SelectMode.NONE) {
                this.me.on('click' + AutoTable.DefaultEventNamespace, 'tbody>tr', function (e) {
                    var $target = $(e.target);
                    if (!$target.is('input')) {
                        var $tr = $(this);
                        //单选时,如果点击是不是已经选择行，则要清除选择
                        if ((scope._opt.selectMode == SelectMode.SINGLE) && (!$tr.hasClass(AutoTable.DefaultSelectedClassName))) {
                            scope.me.find("." + AutoTable.DefaultSelectedClassName).removeClass(AutoTable.DefaultSelectedClassName);
                        }
                        var rowspan = parseInt($target.attr("rowspan") || '1');//若td 无rowspan,则取到undefined,转数字后为NaN
                        while (rowspan >= 1) {
                            $tr.toggleClass(AutoTable.DefaultSelectedClassName);
                            $tr = $tr.next();
                            rowspan--;
                        }
                        scope.me.trigger("autoselected");
                    }
                });
            }
            //页面大小变化,页数变化
            this.me.on('change' + AutoTable.DefaultEventNamespace, 'tfoot select', function (e) {
                let $sel = $(this);
                //修改全局的参数
                if ($sel.hasClass("psSelect")) {
                    if ($sel.val() == "all") {
                        scope._opt.isPageable = false;
                    }
                    else {
                        scope._opt.pageSize = parseInt($sel.val());
                        scope._opt.pageNumber = 1;
                    }
                } else if ($sel.hasClass("pnSelect")) {
                    scope._opt.pageNumber = parseInt($sel.val());
                }
                scope.reloadTbodyTfoot();
            });
            //上一页，下一页
            this.me.on('click' + AutoTable.DefaultEventNamespace, 'tfoot button', function (e) {
                let $sel = $(this);
                if ($sel.data("flag") == "prepage") {
                    scope._opt.pageNumber--;
                } else if ($sel.data("flag") == "nextpage") {
                    scope._opt.pageNumber++;
                } else if ($sel.data("flag") == "paging") {
                    scope._opt.pageNumber = 1;
                    scope._opt.isPageable = true;
                }
                scope.reloadTbodyTfoot();
            });
            //排序
            this.me.on('click' + AutoTable.DefaultEventNamespace, 'thead .autoSort,.autoheader2 .autoSort', function (e) {
                let field = $(this).data("field");
                //方向，true正序  false为倒序
                let direction = scope.sortInfo[field];
                direction = _.isUndefined(direction) ? true : (!direction);
                //保存起来
                scope.sortInfo[field] = direction;
                scope.showData = _.orderBy(scope.showData, [field], direction ? ["asc"] : ["desc"]);
                scope._opt.pageNumber = 1;
                scope.reloadTbodyTfoot();
            });
            //锁定
            if (scope._opt.isScrollable) {
                scope.tContainer.on("scroll" + AutoTable.DefaultEventNamespace, function () {
                    let top = scope.tContainer.scrollTop();
                    let left = scope.tContainer.scrollLeft();
                    scope.frzHead.css({ "transform": `translate(0,${top}px)` });
                    scope.frzTd.css({ "transform": `translate(${left}px,0)` });
                })
            }
            //隐藏及显示事件
            this.me.on('hide' + AutoTable.DefaultEventNamespace, function (e, fields) {
                if (typeof fields == 'undefined') return;  ///表格中带datetimepicker时，也向上冒泡产生show 事件，屏蔽之。
                //传入逗号分隔的字符串，或一个数据。每一项是需要隐藏的列。
                var fArr = fields.split(',');
                _.each(fArr, function (f: string) {
                    if (((_.includes(scope._opt.headerFields, f)) || _.includes(scope._opt.header2Fields, f)) &&  //存在于上下表头列中
                        (!_.includes(scope.hidedFields, f)) //&& 且未隐藏过
                    ) {
                        scope.hidedFields.push(f);
                        var idx = scope.fieldsDict[f].index;
                        var col = scope.me.find("col").eq(idx);
                        var w = col.width();
                        col.data("w", col.width()).addClass('auto-hide-col');
                        _.each(scope.fieldsDict[f].ths, function (th) {
                            var $th = $(th);
                            var hidCount = $th.data('hidCount') || 0;
                            $th.data('hidCount',++hidCount);
                            hidCount == $th.prop('colspan') && $th.addClass('auto-hide-headtext');
                        });
                        w > 0 && scope.me.width('-=' + w);
                    }
                });

            });
            //隐藏及显示事件
            this.me.on('show' + AutoTable.DefaultEventNamespace, function (e, fields) {
                if (typeof fields == 'undefined') return;  ///表格中带datetimepicker时，也向上冒泡产生show 事件，屏蔽之。
                //传入逗号分隔的字符串，或一个数据。每一项是需要隐藏的列。
                var fArr = fields.split(',');
                _.each(fArr, function (f: string) {
                    if (_.includes(scope.hidedFields, f)) //&& 且未隐藏过
                    {
                        _.remove(scope.hidedFields, function (v) { return v == f });
                        var idx = scope.fieldsDict[f].index;
                        var col = scope.me.find("col").eq(idx);
                        var w = col.data('w');
                        w > 0 && scope.me.width('+=' + w);
                        col.removeClass('auto-hide-col');
                        _.each(scope.fieldsDict[f].ths, function (th) {
                            var $th = $(th);
                            var hidCount = $th.data('hidCount') ;
                            $th.data('hidCount', --hidCount);
                            $th.removeClass('auto-hide-headtext');
                        });
                        
                        w > 0 &&  console.log('show', w);
                    }
                });
            });
            //导出
            if (this._opt.isSaveable) {
                this.me.on('export' + AutoTable.DefaultEventNamespace, this.ExportData.bind(this));
                this.me.on('click' + AutoTable.DefaultEventNamespace, '.' + AutoTable.DefaultSaveButtonClassName, this.ExportData.bind(this));
            }
            //树级结构时，点击三角箭头时，要折叠或显示    .auto-expanded  .auto-collapsed
            //切换当前箭头，之后该行向下找到另一个同级的行截止。这之间的所有的下一级缩进全显示，
            //待展开
            if (this._opt.treeField) {
                this.me.on("click" + AutoTable.DefaultEventNamespace, ".auto-collapsed", function () {
                    let $curTri = $(this);
                    let $tr = $curTri.parents("tr");
                    let curIndent: number = $tr.data("indent");
                    //切换当前箭头
                    $curTri.removeClass("auto-collapsed").addClass("auto-expanded");
                    //此处暂时不考虑下表头的问题。  考虑时，要考虑点击是哪个。要折叠或展开，上下表头要同步操作的。想想也复杂
                    $tr.nextAll("tr").each(function (i, tr) {
                        let $tmpTr = $(tr);
                        let tmpIndent = $tmpTr.data("indent");
                        //如果遇到下表头，则跳出。
                        if ($tmpTr.hasClass("autoheader2")) return false;
                        //如果同级上级，则跳出
                        if (tmpIndent <= curIndent) return false;
                        //如果自己的上级是展开的，那么自己就显示 .倒序找上级。
                        let $preTrs = $tmpTr.prevAll("tr");
                        for (var i = $preTrs.length - 1; i >= 0; i--) {
                            let $pTr = $($preTrs.get(i));
                            if ($pTr.hasClass("autoheader2")) break;
                            if ($pTr.data("indent") == tmpIndent - 1) {
                                //如果上级展开，自己显示  此处还有bug
                                if ($pTr.find(".auto-expanded").length > 0) {
                                    $tmpTr.show();
                                    break;
                                }
                            }
                        }
                    });
                });
                //待折叠
                this.me.on("click" + AutoTable.DefaultEventNamespace, ".auto-expanded", function () {
                    let $curTri = $(this);
                    let $tr = $curTri.parents("tr");
                    let curIndent: number = $tr.data("indent");
                    //切换当前箭头
                    $curTri.removeClass("auto-expanded").addClass("auto-collapsed");
                    //此处暂时不考虑下表头的问题。  考虑时，要考虑点击是哪个。要折叠或展开，上下表头要同步操作的。想想也复杂
                    $tr.nextAll("[data-indent]").each(function (i, tr) {
                        let $tmpTr = $(tr);
                        let tmpIndent = $tmpTr.data("indent");
                        //如果同级或上级，则跳出
                        if (tmpIndent <= curIndent) return false;
                        //下表头，则跳出
                        if ($tmpTr.hasClass("autoheader2")) return false;
                        $tmpTr.hide();
                    });
                });
            }
            if (this.isEditable) {
                let scope = this;
                //提交修改
                scope.me.on("focusout" + AutoTable.DefaultEventNamespace, '.autoEditinput', function (e) {
                    //如果有hover框，则隐藏它
                    scope._opt.editTopHover && scope.tEditTopHover.hide();
                    //以下提交数据
                    let $input = $(this);
                    let field = $input.data("field"),
                        kv = $input.data("kv"),
                        old = $input.data("old"),
                        v = $.trim($input.val());
                    if (v == old) return;
                    let upKv = _.clone(kv);
                    //附加上这些值一起上传到后台。这样后台可以用更通用的方法来处理所有的请求。
                    upKv[field] = v;
                    upKv['old_' + field] = old;
                    upKv["_TABLE"] = scope._opt.editTable;
                    upKv["_FIELD"] = field;
                    //如果有验证，则先验证再提交。
                    //同时引发三个事件，附带上传元素，正修改的单元格及成功标志
                    if (_.isNull(scope._opt.editBeforeUpload) || (_.isFunction(scope._opt.editBeforeUpload) && scope._opt.editBeforeUpload(upKv, field))) {
                        scope.me.trigger('beforeCellEdited', [upKv, $input]);
                        $.ajax(scope._opt.editUrl, {
                            cache: false, type: 'POST',
                            data: upKv, dataType: 'json'
                        }).done(function (ret) {
                            $input.data('old', v);
                            //从showdata中找到当前行，把该field的值修改掉
                            _.find(scope.showData, function (row) {
                                if (_.isMatch(row, kv)) { row[field] = v; return true; }
                                return false;
                            });
                            $input.addClass(AutoTable.DefaultCellEdited);
                            scope.me.trigger('cellEdited', [upKv, $input]);
                            scope.me.trigger('afterCellEdited', [upKv, $input, true]);
                        }).fail(function (XMLHttpRequest, textStatus, errorThrown) {
                            $input.val(old);
                            scope.me.trigger('afterCellEdited', [upKv, $input, false]);
                        });
                    }

                });
                //键盘操作
                this.me.on('keydown' + AutoTable.DefaultEventNamespace, '.' + AutoTable.DefaultEditInputClassName, function (event) {
                    let newinput: JQuery = null;
                    if (event.keyCode == 13 || event.keyCode == 39) {  //回车及右
                        let idx = scope.allEditInput.index(this)
                        if (idx < scope.allEditInput.length - 1) newinput = $(scope.allEditInput[idx + 1]);
                    }
                    if (event.keyCode == 37) {  //左
                        let idx = scope.allEditInput.index(this)
                        if (idx > 0) newinput = $(scope.allEditInput[idx - 1]);
                    }
                    if (event.keyCode == 38) {  //上
                        let field = $(this).data('field');
                        let list = scope.allEditInput.filter(`[data-field='${field}']`);
                        let idx = list.index(this);
                        if (idx > 0) newinput = $(list[idx - 1]);
                    }
                    if (event.keyCode == 40) {  //下
                        let field = $(this).data('field');
                        let list = scope.allEditInput.filter(`[data-field='${field}']`);
                        let idx = list.index(this);
                        if (idx < list.length - 1) newinput = $(list[idx + 1]);
                    }
                    if (newinput) {
                        newinput.selectWord();
                        if (!_.isUndefined(newinput[0].scrollIntoView)) {
                            newinput[0].scrollIntoView();
                        }
                        return false;
                    }
                    return true;
                }).on("paste" + AutoTable.DefaultEventNamespace, "." + AutoTable.DefaultEditClassName, function (event) {
                    event.stopPropagation();
                    event.preventDefault();
                    let origEvent: ClipboardEvent = <ClipboardEvent>event.originalEvent;
                    let cd = origEvent.clipboardData || window.clipboardData;
                    if (!cd) return;//firefox会走这吗？

                    let content = cd.getData("text");
                    let rows = content.indexOf('\r\n') == -1 ? content.split('\n') : content.split('\r\n');

                    //如果从excel粘贴，则需要弹出最后一个空元素。如果是纯文本，则最后一个非空，不需要弹出。
                    if ($.trim(rows.slice(-1)[0]) == '') {
                        rows.pop();
                    }
                    let xpos = $(this);
                    let ypos = $(this);
                    let field = xpos.data('field');
                    $.each(rows, function (i, r) {
                        xpos = ypos;
                        let cols = r.split('\t');
                        $.each(cols, function (j, c) {
                            xpos.val($.trim(c)).focus().blur();
                            let idx = scope.allEditInput.index(xpos);
                            if (idx < scope.allEditInput.length - 1) { xpos = $(scope.allEditInput[idx + 1]); }
                            else { return; }  //向后无元素了
                        });
                        let list = scope.allEditInput.filter(`[data-field='${field}']`);
                        let index = list.index(ypos);
                        if (index < list.length - 1) ypos = $(list[index + 1]);
                        else return;   //向下无元素了
                    });
                });
                //编辑需要显示hover部分时,每次输入都要重新显示内容。
                if (scope._opt.editTopHover) {
                    this.me.on("input" + AutoTable.DefaultEventNamespace, '.autoEditinput', function () {
                        var result = $(this).val();
                        scope.tEditTopHover.text(scope.fmtNum(result))
                    });
                    this.me.on("focusin" + AutoTable.DefaultEventNamespace, '.autoEditinput', function () {
                        var $ipt = $(this);
                        var offset = $ipt.offset();
                        var result = $ipt.val();
                        setTimeout(function () {
                            scope.tEditTopHover.css({ top: offset.top - 35, left: offset.left })
                                .css("min-width", $ipt.outerWidth())
                                .text(scope.fmtNum(result)).show();
                        }, 200);
                    });
                }
            }

            //每次load reloaded都要重定位滚动
            this.me.on(`loaded${AutoTable.DefaultEventNamespace} reloaded${AutoTable.DefaultEventNamespace}`, function () {
                if (scope._opt.isScrollable)
                    scope.tContainer.trigger("scroll");
            });
        }
        //格式化数字，整数部分大于4位，则用千分位表示，小数部分每两位加个空格
        private fmtNum(inNum: number | string): string {
            let fst = "", snd = "";
            let arr: string[];
            let num = inNum.toString().replace(/\s+/g, '');
            if (!/^[-]?[0-9]+(.[0-9]*)?$/.test(num)) return <string>inNum;
            let [a, b] = num.split(".");
            //处理整数
            a[0] == '-' && (fst = '-') && (a = a.slice(1));
            if (a.length > 4) {
                let len = a.length % 3 ? a.length % 3 : 3;
                fst += (a.slice(0, len))
                a = a.slice(len);

                while (a.length) {
                    fst += ("," + a.slice(0, 3));
                    a = a.slice(3);
                }
            } else {
                fst += a;
            }

            //处理小数
            if (b && b.length > 1) {
                snd = '.';
                while (b.length) {
                    snd += (b.slice(0, 2) + " ");
                    b = b.slice(2);
                }
            }
            return fst + snd;
        }
        /**导出报表结果*/
        public ExportData() {
            let scope = this;
            var cloneOpt = $.extend(true, {}, this._opt);
            var showData = JSON.parse(JSON.stringify(this.showData));
            //表头里的标签，比如换行和上标，要在后台进行处理
            var uData: any = _.pick(cloneOpt, "header", "header2",
                "headerFields", "header2Fields", "freezeFields");
            var allfield = _.uniq(cloneOpt.header2Fields.concat(cloneOpt.headerFields));
            var rownum = 1;
            //移除表头里的_catch
            _.each(uData.header, function (row) {
                _.each(row, function (th) {
                    delete th._cache;
                });
            });
            _.each(uData.header2, function (row) {
                _.each(row, function (th) {
                    delete th._cache;
                });
            });
            //重新考虑excel的因素，把tbody的内容重新格式化一次
            _.each(showData, function (row) {
                _.each(allfield, function (key: string) {
                    if (key == "rownum")
                        row[key] = rownum++;
                    else {
                        //显示值的输出。根据columns.formator来格式化数据到uData里
                        if (scope._opt.columns[key] && _.isFunction(scope._opt.columns[key].formator)) {
                            row[key] = scope._opt.columns[key].formator(row[key], row)
                            if (row[key] == null) {
                                row[key] = "";
                            } else {
                                //数据部分要替换掉html标签
                                row[key] = row[key].replace(/<[^>]+>/g, "");
                            }
                        }
                        else
                            row[key] = (row[key] === 0 ? scope._opt.zeroShowAs : (row[key] || scope._opt.nullShowAs));//值为0，显示为0. null，不显示。
                    }
                });
            });
            uData.showData = showData;
            //构造一个下载窗体.
            var $iframe = $('<iframe />');
            var $form = $('<form target="down-file-iframe" method="post" />');
            $form.attr('action', this._opt.saveURL);
            var size = 84555;
            var content = JSON.stringify(uData);
            var d = 0;
            //上传的name 是 d1,d2。。。。
            while (content.length > 0) {
                d++;
                $form.append(`<input  name='d${d}' value='${content.slice(0, size)}' />`);
                content = content.slice(size);
            }
            $form.append(`<input  name='cnt' value='${d}' />`);
            $iframe.append($form);
            $(document.body).append($iframe);
            (<HTMLFormElement>$form[0]).submit();
            $iframe.remove();
        }

        /**有树时，要隐藏一下相关的行*/
        private hideTreeCollapse() {
            let scope = this;
            if (this._opt.treeField != "") {
                scope.me.find("tr[data-indent]").each(function (i, tr) {
                    let $tr = $(tr);
                    $tr.toggle($tr.data("indent") <= scope._opt.treeIndentShowLevel);
                });
            }
        }
    }
}